package com.medusa.aps.business.modules.board.mp.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.medusa.aps.business.model.pagination.Page;
import com.medusa.aps.business.modules.board.model.ProcessStatus;
import com.medusa.aps.business.modules.board.model.dto.WorkshopProductionStatisticsDTO;
import com.medusa.aps.business.modules.board.model.input.WorkshopProductionDetailMultiQueryParam;
import com.medusa.aps.business.modules.board.model.input.WorkshopProductionDetailSingleQueryParam;
import com.medusa.aps.business.modules.board.model.input.WorkshopProductionStatisticsParam;
import com.medusa.aps.business.modules.board.model.output.*;
import com.medusa.aps.business.modules.board.mp.entity.WorkshopProductionStatistics;
import com.medusa.aps.business.modules.board.mp.mapper.IWorkshopProductionStatisticsMapper;
import com.medusa.aps.business.modules.board.mp.service.IWorkshopProductionService;
import com.medusa.aps.business.modules.modeling.mp.entity.Equipment;
import com.medusa.aps.business.util.DateTimeUtils;
import com.medusa.aps.business.util.WorkshopStatUtils;
import com.medusa.aps.business.common.fastjson2.FastJson2;
import io.vavr.Tuple;
import io.vavr.Tuple2;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>车间维度生产任务服务接口实现类</p>
 *
 * @author An.Yan
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class WorkshopProductionServiceImpl extends ServiceImpl<IWorkshopProductionStatisticsMapper, WorkshopProductionStatistics> implements IWorkshopProductionService {

    private final IWorkshopProductionStatisticsMapper mapper;

    /**
     * 分页查询车间维度排产任务统计数据
     *
     * @param queryParam {@link WorkshopProductionStatisticsParam}
     * @return {@link WorkshopProductionStatisticsVO}
     */
    @Override
    public WorkshopProductionStatisticsVO pageWorkshopProductionTaskStatistics(WorkshopProductionStatisticsParam queryParam) {
        queryParam.validateParam();
        WorkshopProductionStatisticsVO result = new WorkshopProductionStatisticsVO();

        // 根据起始时间生成日期表头
        SortedSet<LocalDate> dateSet = DateTimeUtils.generateDatesHeader(queryParam.getDateRange().getStart(), queryParam.getDateRange().getEnd());
        result.setHeader(dateSet);

        // 分页查询不重复的车间ID
        Page<Long> noDuplicatedWorkshopIds = this.mapper.selectNonDuplicatedWorkshopIds(queryParam);
        if (noDuplicatedWorkshopIds == null || CollUtil.isEmpty(noDuplicatedWorkshopIds.getRecords())) {
            return result;
        }

        List<WorkshopProductionStatisticsDTO> workshopProductionStatisticsDTOPage = mapper.selectPage(noDuplicatedWorkshopIds.getRecords().stream().collect(Collectors.toSet()), queryParam.getDateRange());

        // 车间, 未排产设备数量
        Map<Integer, Long> availableEquipmentMap = this.statisticsAvailableEquipment(noDuplicatedWorkshopIds.getRecords());


        // key:车间ID + "_" + 统计时间, value: 统计数据
        Map<String, WorkshopProductionStatisticsDTO> map = workshopProductionStatisticsDTOPage.stream()
                .collect(Collectors.toMap(k -> k.getWorkshopId() + StrPool.UNDERLINE + k.getStatisticsDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")), a -> a, (a, b) -> a));

        // 过滤不重复的车间统计数据
        Set<WorkshopProductionStatisticsDTO> workshopSet = workshopProductionStatisticsDTOPage
                .stream().collect(Collectors.toSet());

        List<WorkshopProductionRowData> rowsData = new ArrayList<>();

        // 以车间为维度 设置统计数据
        workshopSet.forEach(workshop -> {
            WorkshopProductionRowData rowData = new WorkshopProductionRowData().setWorkshopId(workshop.getWorkshopId())
                    .setWorkshopCode(workshop.getWorkshopCode()).setWorkshopName(workshop.getWorkshopName());

            dateSet.forEach(date -> {
                String key = workshop.getWorkshopId() + StrPool.UNDERLINE + date;
                WorkshopProductionStatisticsDTO statisticsDTO = map.get(key);
                WorkshopProductionItemData itemStatisticsData = new WorkshopProductionItemData(date);
                int availableEquipmentCnt = availableEquipmentMap.getOrDefault(workshop.getWorkshopId(), 0L).intValue();
                if (statisticsDTO == null) {
                    itemStatisticsData.setUnscheduledEquipmentCnt(availableEquipmentCnt);
                } else {
                    itemStatisticsData
                            .setPlannedCnt(statisticsDTO.getPlannedCnt())
                            .setScheduledEquipmentCnt(statisticsDTO.getScheduledEquipmentCnt())
                            .setUnscheduledEquipmentCnt(availableEquipmentCnt >= statisticsDTO.getScheduledEquipmentCnt() ?
                                    availableEquipmentCnt - statisticsDTO.getScheduledEquipmentCnt() : 0)
                            .setAssociatedOrderCnt(statisticsDTO.getAssociatedOrderCnt());
                }
                rowData.put(date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")), itemStatisticsData);
            });
            rowsData.add(rowData);
        });
        IPage<WorkshopProductionRowData> page = new Page<>(queryParam.getCurrent(), noDuplicatedWorkshopIds.getSize());
        page.setRecords(rowsData);
        page.setTotal(rowsData.size());
        result.setRows(page);
        return result;
    }

    /**
     * 分页查询车间生产详情-单日
     *
     * @param queryParam 查询参数,参考 {@link WorkshopProductionDetailSingleQueryParam}
     * @return 车间生产详情分页数据, 参考 {@link WorkshopProductionDetailVO}
     */
    @Override
    public Page<WorkshopProductionDetailVO> pageWorkshopProductionDetail(WorkshopProductionDetailSingleQueryParam queryParam) {
        Page<WorkshopProductionDetailVO> result = this.mapper.selectDetailsPage(queryParam);
        LocalDate statDate = queryParam.getStatDate();

        // 移除掉排程明细中时间段不包含统计时间的数据
        result.getRecords().removeIf(item -> {
            SortedMap<LocalDate, Duration> durationSortedMap = DateTimeUtils.strToDurationMap(item.getSpecificTime());
            return !durationSortedMap.containsKey(statDate);
        });
        result.getRecords().forEach(detail -> {
            // 计算计划加工状态&实际报工状态
            detail.setPlannedProcessingStatus(ProcessStatus.plan(detail.getPlannedStartTime(), detail.getPlannedEndTime()));
            calculateStatisticsFields(statDate, detail);

            // 设置计划开始和结束时间
            Tuple2<LocalDateTime, LocalDateTime> startAndEndTime = calculateStartAndEndTime(statDate, detail.getSpecificTime());
            if (startAndEndTime._1 != null) {
                detail.setPlannedStartTime(startAndEndTime._1);
            }
            if (startAndEndTime._2 != null) {
                detail.setPlannedEndTime(startAndEndTime._2);
            }
        });
        //result.setTotal()
        return result;
    }

    /**
     * 计算给定的统计日期在时间段中的最早和最晚的时间并返回.如果统计日期不在时间段内,则返回空.
     * @param statDate 统计日期
     * @param specificTime 时间段
     * @return {@link Tuple2}
     */
    private Tuple2<LocalDateTime, LocalDateTime> calculateStartAndEndTime(LocalDate statDate, String specificTime) {
        Tuple2<LocalDateTime, LocalDateTime> result = Tuple.of(null, null);
        if (StrUtil.isEmpty(specificTime)) {
            return result;
        }
        //去掉前后的[]
        String substring = specificTime.substring(1, specificTime.length() - 1);
        List<String> strList = StrUtil.split(substring, StrPool.COMMA);
        if (CollUtil.isEmpty(strList)) {
            return result;
        }
        SortedSet<LocalDateTime> timePeriodSet = new TreeSet<>();
        strList.forEach(str -> {
            String[] split = str.trim().split("~");
            LocalDateTime startTime = LocalDateTime.parse(split[0], FastJson2.NO_SECONDS_DATETIME_FORMATTER);
            LocalDateTime endTime = LocalDateTime.parse(split[1], FastJson2.NO_SECONDS_DATETIME_FORMATTER);
            if (startTime.toLocalDate().equals(statDate)) {
                timePeriodSet.add(startTime);
            }
            if (endTime.toLocalDate().equals(statDate)) {
                timePeriodSet.add(endTime);
            }
        });
        if (CollUtil.isEmpty(timePeriodSet) || timePeriodSet.size() < 2) {
            return result;
        }
        return Tuple.of(timePeriodSet.first(), timePeriodSet.last());

    }


    /**
     * 计算指定日期的计划数量&已完工数量&工序进度
     * @param currDay 指定统计日期
     * @param detailVO 统计对象
     */
    private void calculateStatisticsFields(LocalDate currDay, WorkshopProductionDetailVO detailVO) {
        // 将时间段转为TreeMap, key:日期; value: Duration
        SortedMap<LocalDate, Duration> localDateDurationMap = DateTimeUtils.strToDurationMap(detailVO.getSpecificTime());
        if (!localDateDurationMap.containsKey(currDay)) {
            detailVO.setPlannedQuantity(new BigDecimal(0));
            return;
        }
        // 排程明细中时间段累计分钟
        Long totalMinutes = localDateDurationMap.values().stream().mapToLong(e -> e.toMinutes()).sum();

        // 累计的计划数量
        BigDecimal totalPlannedCnt = new BigDecimal(0);

        // 剩余的计划数量
        BigDecimal remainingPlannedCnt;

        for (Iterator<LocalDate> it = localDateDurationMap.keySet().iterator(); it.hasNext();) {
            LocalDate date = it.next();
            Duration duration = localDateDurationMap.get(date);
            remainingPlannedCnt = detailVO.getPlannedQuantity().subtract(totalPlannedCnt);

            // 计算当日计划数量
            BigDecimal plannedCnt = WorkshopStatUtils.calculatePlannedCnt(detailVO.getPlannedQuantity(), totalMinutes, duration.toMinutes());
            if (remainingPlannedCnt.compareTo(plannedCnt) < 0) {
                plannedCnt = remainingPlannedCnt;
            }
            if (it.hasNext()) {
                totalPlannedCnt = totalPlannedCnt.add(plannedCnt);
            }

            // 如果日期匹配,设置当日已完成数量&工序进度
            if (date.equals(currDay)) {
                detailVO.setPlannedQuantity(!localDateDurationMap.lastKey().equals(currDay) ? plannedCnt :
                        detailVO.getPlannedQuantity().subtract(totalPlannedCnt));
                break;
            }
        }
    }

    /**
     * 分页查询车间多日生产详情
     *
     * @param queryParam 查询参数,参考 {@link WorkshopProductionDetailMultiQueryParam}
     * @return 车间生产详情分页数据, 参考 {@link WorkshopProductionDetailVO}
     */
    @Override
    public Page<WorkshopProductionDetailVO> pageWorkshopProductionDetail(WorkshopProductionDetailMultiQueryParam queryParam) {
        queryParam.validateParam();

        // 生成日期范围
        SortedSet<LocalDate> dateRange = DateTimeUtils.generateDatesHeader(queryParam.getDateRange().getStart(), queryParam.getDateRange().getEnd());

        // 获取原始数据
        Page<WorkshopProductionDetailVO> result = this.mapper.selectDetailsPage(queryParam);

        // 遍历日期范围,统计数据
        dateRange.stream().forEach(statDate -> {
            result.getRecords().forEach(detail -> {

                // 多日详情,不需要计算相关字段
                //calculateStatisticsFields(statDate, detail);

                // 计算计划加工状态&实际报工状态
                detail.setPlannedProcessingStatus(ProcessStatus.plan(detail.getPlannedStartTime(), detail.getPlannedEndTime()));

            });
        });
        return result;
    }

    /**
     * 统计给定的车间ID集合在指定的日期已排产设备数量以及系统可用设备总数
     *
     * @param workshopId 车间ID集合
     * @param statDate    统计时间
     * @return {@link WorkshopProductionEquipmentVO}集合
     */
    @Override
    public List<WorkshopProductionEquipmentVO> statisticsEquipment(Integer workshopId, LocalDate statDate) {
        if (workshopId == null || statDate == null) {
            return new ArrayList<>();
        }
        return this.mapper.statisticsEquipment(workshopId, statDate);
    }

    /**
     * 获取指定车间的可用设备数量,如果 {@code workshopIds}为空,则返回所有车间可用设备数量
     * @param workshopIds 车间ID集合
     * @return 指定车间的可用设备数量
     */
    @Override
    public Map<Integer, Long> statisticsAvailableEquipment(List<Long> workshopIds) {
       return Optional.ofNullable(this.mapper.statisticsAvailableEquipment(workshopIds)
                        .stream().collect(Collectors.groupingBy(Equipment :: getWorkshopId, Collectors.counting())))
                .orElse(new HashMap<>());
    }

    /**
     * 根据workshopID + statDate 查询统计数据
     *
     * @param statisticsEntities 统计数据参数
     * @return 与workshopID + statDate相匹配的统计数据
     */
    @Override
    public List<WorkshopProductionStatistics> selectStatisticsByCondition(List<WorkshopProductionStatistics> statisticsEntities) {
       Boolean validParam = Optional.ofNullable(statisticsEntities)
               .map(e -> CollUtil.isNotEmpty(
                       e.stream().filter(i -> i.getWorkshopId() != null && i.getStatisticsDate() != null).collect(Collectors.toList()))
               )
               .orElse(false);
       if (!validParam) {
           return new ArrayList<>();
       }
       return mapper.selectStatisticsByCondition(statisticsEntities);
    }

    /**
     * 批量保存统计数据,根据workshopId和statisticsDate两个字段检查统计数据是否存在,若存在则更新;否则插入
     * @param statisticsEntities 待保存的统计数据
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveStatisticsData(List<WorkshopProductionStatistics> statisticsEntities) {
        LocalDate now = LocalDate.now();
        // 需要修正的数据,这批数据是指: 车间生产统计表中, 不等于本次统计数据(workshop_id + stat_date)的所有数据
        // TODO 后面考虑分批处理
        Set<WorkshopProductionStatistics> adjustedSet = new HashSet<>();
        List<WorkshopProductionStatistics> nonExistentStatisticsData = this.mapper.selectNonExistentStatisticsData(statisticsEntities);
        adjustedSet.addAll(nonExistentStatisticsData);
        List<List<WorkshopProductionStatistics>> sourceList = Lists.partition(statisticsEntities, 1000);

        // 分批处理统计数据
        sourceList.stream().forEach(itemList -> {
            Map<String, WorkshopProductionStatistics> itemMap =
                    itemList.stream().collect(Collectors.toMap(k -> k.getWorkshopId() + StrPool.UNDERLINE + k.getStatisticsDate(), a -> a, (a, b) -> a));

            List<WorkshopProductionStatistics> alreadyExistData = this.selectStatisticsByCondition(itemList);
            List<WorkshopProductionStatistics> waitingInsert = itemList.stream().filter(e -> !alreadyExistData.contains(e)).collect(Collectors.toList());
            List<WorkshopProductionStatistics> waitingUpdate = new ArrayList<>();
            if (!CollUtil.isEmpty(waitingInsert)) {
                this.saveBatch(waitingInsert);
            }
            if (!CollUtil.isEmpty(alreadyExistData)) {
                alreadyExistData.forEach(u -> {
                    String key = u.getWorkshopId() + StrPool.UNDERLINE + u.getStatisticsDate();
                    WorkshopProductionStatistics entity = itemMap.get(key);
                    if (entity == null) {
                        return;
                    }
                    u.setPlannedCnt(entity.getPlannedCnt());
                    u.setAssociatedOrderCnt(entity.getAssociatedOrderCnt());
                    if (!u.getStatisticsDate().isBefore(now)) {
                        u.setScheduledEquipmentCnt(entity.getScheduledEquipmentCnt());
                        u.setUnscheduledEquipmentCnt(entity.getUnscheduledEquipmentCnt());
                    }
                    waitingUpdate.add(u);
                });
                this.updateBatchById(waitingUpdate);
            }
        });

        // 如果待校正的集合非空,则批量修复adjustList
        if (CollUtil.isNotEmpty(adjustedSet)) {
            // 系统可用的设备总数
            Map<Integer, Long> availableEquipmentMap = this.statisticsAvailableEquipment(null);
            BigDecimal initialData = new BigDecimal(0);
            adjustedSet.forEach(item -> {
                Integer availableEquipmentCnt = availableEquipmentMap.getOrDefault(item.getWorkshopId(), 0L).intValue();
                item.setPlannedCnt(initialData);
                item.setScheduledEquipmentCnt(initialData);
                item.setAssociatedOrderCnt(initialData.intValue());
                item.setUnscheduledEquipmentCnt(new BigDecimal(availableEquipmentCnt));
            });
            this.updateBatchById(adjustedSet);
            log.info("修正的数据条数：{}", adjustedSet.size());
        }
    }
}
